
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.UUID;
import java.util.logging.Logger;

import com.google.gson.Gson;


public class Server{

	
	private static final int RX_BUFFER = 4096;
	private static final float MINIMIM_BID = 5;
	
	Gson gson;
	int port;
	DatagramSocket sendSocket,receiveSocket;
	byte[] data;
	DatagramPacket packet;
	DataStore userData;
	FIFOQueue<Message> requestQ;
	FIFOQueue<Message> responseQ;
	ArrayList<Auction> auctionQ;

	private final static Logger LOGGER = Logger.getLogger(Server.class .getName());

	
	boolean validateMessage(String Type, Message obj){
		
		if(Type.equals("REGISTER")){
			
			"REGISTER".equals(obj.type);		
			
		}else if(Type.equals("DEREGISTER")){
			
			
		}
	
		return true;
	}
	
	public Server(int port) throws SocketException, ClassNotFoundException, SQLException{
		gson = new Gson();
		receiveSocket = new DatagramSocket(port);
		sendSocket = new DatagramSocket(null);
		receiveSocket.setSoTimeout(3000);
		sendSocket.setSoTimeout(3000);
		data = new byte[RX_BUFFER];
		packet = new DatagramPacket(data, data.length);
		userData = new DataStore();
		requestQ = new FIFOQueue<Message>();
		responseQ = new FIFOQueue<Message>();
		auctionQ = new ArrayList<Auction>();

	}
	
	void encodeAndSend(Message Obj) throws IOException{

		InetAddress address = InetAddress.getByName(Obj.ipAddress);
		//sendSocket.connect(address,Obj.port);
		String outgoingMessage = gson.toJson(Obj);
		System.out.println("sending message ---> "+outgoingMessage);
		byte[] outputdata = outgoingMessage.getBytes();
		packet = new DatagramPacket(outputdata, outputdata.length, address, Obj.port);
		sendSocket.send(packet);
		//sendSocket.disconnect();
	}
	
	public void requestHandler(Message requestObj) throws SQLException, IOException{
		System.out.println("Handling request ---> "+requestObj.type);
		
		if("REGISTER".equals(requestObj.type) && (requestObj.rqId != 0)){
			
			Message responseObj = new Message();
			if(userData.clientExists(requestObj.name) == true){
				
				//create response
				responseObj.type = "UNREGISTERED"; 
				responseObj.reason = "User already exists";
				responseObj.port = requestObj.port;
				responseObj.rqId = requestObj.rqId;
				responseObj.ipAddress = requestObj.ipAddress;
				responseQ.put(responseObj);
				
			}else{
				
				userData.addClient(requestObj.name, requestObj.ipAddress, requestObj.port);			
				requestObj.type = "REGISTERED";
				responseQ.put(requestObj);

				System.out.println("added registered message to the responseQ.--->"+this.getClass());
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			

		}else if("REGISTER".equals(requestObj.type) && requestObj.rqId == 0){
			
			Message responseObj = new Message();
	
			//create response
			responseObj.type = "UNREGISTERED"; 
			responseObj.reason = "Invlaid Registration Request";
			responseObj.port = requestObj.port;
			responseObj.rqId = requestObj.rqId;
			responseObj.ipAddress = requestObj.ipAddress;
			
			responseQ.put(responseObj);
			
		}else if("DEREGISTER".equals(requestObj.type) &&  requestObj.rqId != 0){
			
			if(userData.clientIsSellingItem(requestObj.name)){
				
				Message responseObj = new Message();
				
				//create response
				responseObj.type = "DEREG_DENIED"; 
				responseObj.reason = "You have one or more unsold items";
				responseObj.port = requestObj.port;
				responseObj.rqId = requestObj.rqId;
				responseObj.ipAddress = requestObj.ipAddress;
				
				responseQ.put(responseObj);
				
			}else if(userData.clientIsLeadBidder(requestObj.name)){
				
				Message responseObj = new Message();
				
				//create response
				responseObj.type = "DEREG_DENIED"; 
				responseObj.reason = "You are the lead bidder on one or more items";
				responseObj.port = requestObj.port;
				responseObj.rqId = requestObj.rqId;
				responseObj.ipAddress = requestObj.ipAddress;
				
				responseQ.put(responseObj);
			}
			else if(userData.clientExists(requestObj.name) == false){
				
				Message responseObj = new Message();
				
				//create response
				responseObj.type = "DEREG_DENIED"; 
				responseObj.reason = "You were not registered with this server";
				responseObj.port = requestObj.port;
				responseObj.rqId = requestObj.rqId;
				responseObj.ipAddress = requestObj.ipAddress;
				
				responseQ.put(responseObj);
				
			
			}else{
				
				userData.removeClient(requestObj.name);
				//userData.loadUsers();
				
				Message responseObj = new Message();
				
				//create response
				responseObj.type = "DEREG_CONF"; 
				responseObj.reason = "you have sucessfully left the auction";
				responseObj.port = requestObj.port;
				responseObj.rqId = requestObj.rqId;
				responseObj.ipAddress = requestObj.ipAddress;
				
				responseQ.put(responseObj);
			}
				
		}else if("DEREGISTER".equals(requestObj.type) && requestObj.rqId == 0){
					
			Message responseObj = new Message();
			
			//create response
			responseObj.type = "DEREG_DENIED"; 
			responseObj.reason = "Invalid Dereg Request";
			responseObj.port = requestObj.port;
			responseObj.rqId = requestObj.rqId;
			responseObj.ipAddress = requestObj.ipAddress;
			
			responseQ.put(responseObj);
			
		}
		else if("OFFER".equals(requestObj.type)){
			
			System.out.println("Offer recieved!");
			
			//chk if min bid is acceptable
			if(Float.valueOf(requestObj.bid) >= MINIMIM_BID){
				
				// chk for duplicates i.e. match by description.
				// if duplicate, ignore OFFER msg.
				if(userData.itemExists(requestObj.description)){
					
					System.out.println("item exist, ignoring offer message");
										
				}else{

					//generate item number
				    UUID itemId = UUID.randomUUID();
					
				    //add item to DB
				    userData.addItem(itemId.toString(), requestObj.description, Float.valueOf(requestObj.bid), requestObj.name, requestObj.ipAddress, requestObj.port);
				    
				    //respond with offer confirm message
				    Message offerConf = new Message();
				    offerConf.type = "OFFER-CONF";
				    offerConf.itemId = itemId.toString();
				    offerConf.bid = requestObj.bid;
				    offerConf.description = requestObj.description;
				    offerConf.ipAddress =  requestObj.ipAddress;
				    offerConf.port = requestObj.port;
			    	
					responseQ.put(offerConf);
				    
				    
					//send out new item message to all clients /w itemId
				    for(User u : userData.userList){
				    	
				    	if(!requestObj.name.equals(u.name)){
				    	
					    	Message newItemMsg = new Message();
					    	newItemMsg.type = "NEW-ITEM";
					    	newItemMsg.itemId = itemId.toString();
					    	newItemMsg.bid = requestObj.bid;
					    	newItemMsg.description = requestObj.description;
					    	newItemMsg.ipAddress =  u.ipAddress;
					    	newItemMsg.port = u.port;
					    	
							responseQ.put(newItemMsg);
				    	}else{
				    		System.out.println(u.name+" is the seller so he does'nt recieve a NEW-ITEM only an OFFER-CONF");
				    	}

				    }
				    
				    //start Bid on item
				    Item item = new Item(itemId.toString(), requestObj.description, Float.valueOf(requestObj.bid),Float.valueOf(requestObj.bid));
				    User seller = new User(requestObj.name, requestObj.ipAddress, requestObj.port);
				    Auction auction = new Auction(item, seller, this);
				    auction.start();
				    auctionQ.add(auction);
				}
	
			}
			else{
			
				System.out.println("item offer for less than minimum bid, ignoring ..");
			}
					
		}else if("BID".equals(requestObj.type)){
			
			System.out.println("Bid recieved!");
			
			//check against current bid.
			Item item = userData.getItemById(requestObj.itemId);
			
			if(item != null){
				if(item.currentBid < Float.valueOf(requestObj.bid)){
					
					//update bid.
					userData.updateItem(requestObj.itemId,Float.valueOf(requestObj.bid),requestObj.name, requestObj.ipAddress, requestObj.port);
					userData.reloadItems();
					updateAuction(requestObj);
					
					//send out highest message.
					for(User u : userData.userList){
				    	
				    	Message newItemMsg = new Message();
				    	newItemMsg.type = "HIGHEST";
				    	newItemMsg.itemId = requestObj.itemId;
				    	newItemMsg.bid = requestObj.bid;
				    	newItemMsg.ipAddress =  u.ipAddress;
				    	newItemMsg.port = u.port;
				    	
						responseQ.put(newItemMsg);
	
				    }
				}
			}
			
		}
		else if("GETITEMS".equals(requestObj.type)){
			
			System.out.println("recieved request for item list");
			
			userData.reloadItems();
			
			Message serverItemList = new Message();
			serverItemList.type = "ITEMLIST";
			serverItemList.items = userData.itemList;
			serverItemList.port = requestObj.port;
			serverItemList.ipAddress = requestObj.ipAddress;
			
			responseQ.put(serverItemList);
			
		}
		else if("GETMYITEMS".equals(requestObj.type)){
			
			System.out.println("recieved request for item list");
			
			userData.getSellerItems(requestObj.name);
			
			Message serverItemList = new Message();
			serverItemList.type = "ITEMLIST";
			serverItemList.items = userData.sellersItemList;
			serverItemList.port = requestObj.port;
			serverItemList.ipAddress = requestObj.ipAddress;
			
			responseQ.put(serverItemList);
			
		}
		else{
			LOGGER.info("Invalid Request received "+requestObj.type+" ignoring...");
		}
		
	}
	
	private void updateAuction(Message requestObj) {
		// TODO Auto-generated method stub
		System.out.println("Updating auction ...");
		
		for(Auction a : auctionQ){
		
			if(a.item.itemId.equals(requestObj.itemId)){
				
				System.out.println("updating "+a.item.description+" seller = "+a.seller.name);
				a.item.currentBid = Float.valueOf(requestObj.bid);
				a.winner.name = requestObj.name;
				a.winner.ipAddress = requestObj.ipAddress;
				a.winner.port = requestObj.port;
				
				if(a.hasBid(a.winner) == false){
					a.bidderList.add(a.winner);
				}
			}
		}
	}

	public Message recieve()throws IOException{

			//block on socket for an incoming message;
			Message requestObj = new Message();
			data = new byte[RX_BUFFER];
			packet = new DatagramPacket(data, data.length);
			receiveSocket.receive(packet);
			String rx = "bad packet";
			try {
				rx = new String(packet.getData(),"UTF-8");
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				// e.printStackTrace();
				System.out.println(e.getMessage());
			}
			String incomingMessage = rx.trim();
			System.out.println("recieved message = "+incomingMessage);
			requestObj = gson.fromJson(incomingMessage, Message.class);
			requestObj.ipAddress = packet.getAddress().toString().split("/")[1];
			requestObj.port = packet.getPort()-1;
			System.out.println("recieved message from "+requestObj.ipAddress+":"+requestObj.port);
			return requestObj;
	}
		
	public void listenerThread() {
		// TODO Auto-generated method stub
				
		//thread1 receive loop
		//save shit in queue
		while (true)
		{  
			Message incomingMessage = new Message();
			incomingMessage.type = null;
			try {
				//block on receive
				incomingMessage = this.recieve();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				// e.printStackTrace();
				System.out.println(e.getMessage());
				//break;
			}
			if(incomingMessage.type != null){
				//add stuff to FIFO queue
				System.out.println("Adding "+incomingMessage.type+" to requestQ");
				this.requestQ.put(incomingMessage);
			}
		}
		
	}
	
	public void requestHandlerThread(){
		
		while(true){
			
			if(requestQ.isEmpty()){
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else{
				try {
					System.out.println("handling request --->"+requestQ.peek().type+" from "+requestQ.peek().ipAddress+" : "+requestQ.peek().port);
					requestHandler(requestQ.get());
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		}
	}
	
	public void responderThread(){
		
		while(true){
			
			if(responseQ.isEmpty()){
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else{
				
				try {
					encodeAndSend(responseQ.get());
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
		}
		
	}
	
	public void shutdown(){
		
		sendSocket.close();
		receiveSocket.close();
	}
	
	public void start(){
		
		//restore auction
		for(Auction a :userData.auctionList){
		
			Item item = new Item(a.item.itemId, a.item.description, a.item.startBid, a.item.currentBid);
			User seller = new User(a.seller.name, a.seller.ipAddress, a.seller.port);
			Auction auction = new Auction(item, seller, this);
			auction.start();
			auctionQ.add(auction);
		
		}
		
		//listener thread
		new Thread(
	            new Runnable() {
	                public void run() {
	            		System.out.println("Listener started.--->"+this.getClass());
	                    listenerThread();
	                    
	               }
	            }
	        ).start();
		
		//request handler thread
		new Thread(
	            new Runnable() {
	                public void run() {
	            		System.out.println("RequestHandler started.--->"+this.getClass());
	                    requestHandlerThread();
	                }
	            }
	        ).start();
		
		//responder thread
		new Thread(
	            new Runnable() {
	                public void run() {
	            		System.out.println("Responder started.--->"+this.getClass());
	                    responderThread();
	                }
	            }
	        ).start();
		
	}
	
	public static void main(String args[]) throws Exception
	{ 
		
		//Server Thread
		System.out.println("Server started.--->(main Thread)");
		final Server testServer = new Server(2006);
		testServer.start();
				
		//TODO implement GUI
				
		//let threads run.
		while(true){
			Thread.sleep(1000);
		}

	}

}
